/*
 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.lang.invoke;

import java.util.Arrays;

import static java.lang.invoke.LambdaForm.*;
import static java.lang.invoke.MethodHandleStatics.*;

/**
 * A method handle whose invocation behavior is determined by a target.
 * The delegating MH itself can hold extra "intentions" beyond the simple behavior.
 *
 * @author jrose
 */
/*non-public*/
abstract class DelegatingMethodHandle extends MethodHandle {

  protected DelegatingMethodHandle(MethodHandle target) {
    this(target.type(), target);
  }

  protected DelegatingMethodHandle(MethodType type, MethodHandle target) {
    super(type, chooseDelegatingForm(target));
  }

  protected DelegatingMethodHandle(MethodType type, LambdaForm form) {
    super(type, form);
  }

  /**
   * Define this to extract the delegated target which supplies the invocation behavior.
   */
  abstract protected MethodHandle getTarget();

  @Override
  abstract MethodHandle asTypeUncached(MethodType newType);

  @Override
  MemberName internalMemberName() {
    return getTarget().internalMemberName();
  }

  @Override
  boolean isInvokeSpecial() {
    return getTarget().isInvokeSpecial();
  }

  @Override
  Class<?> internalCallerClass() {
    return getTarget().internalCallerClass();
  }

  @Override
  MethodHandle copyWith(MethodType mt, LambdaForm lf) {
    // FIXME: rethink 'copyWith' protocol; it is too low-level for use on all MHs
    throw newIllegalArgumentException("do not use this");
  }

  @Override
  String internalProperties() {
    return "\n& Class=" + getClass().getSimpleName() +
        "\n& Target=" + getTarget().debugString();
  }

  @Override
  BoundMethodHandle rebind() {
    return getTarget().rebind();
  }

  private static LambdaForm chooseDelegatingForm(MethodHandle target) {
    if (target instanceof SimpleMethodHandle) {
      return target.internalForm();  // no need for an indirection
    }
    return makeReinvokerForm(target, MethodTypeForm.LF_DELEGATE, DelegatingMethodHandle.class,
        NF_getTarget);
  }

  static LambdaForm makeReinvokerForm(MethodHandle target,
      int whichCache,
      Object constraint,
      NamedFunction getTargetFn) {
    String debugString;
    switch (whichCache) {
      case MethodTypeForm.LF_REBIND:
        debugString = "BMH.reinvoke";
        break;
      case MethodTypeForm.LF_DELEGATE:
        debugString = "MH.delegate";
        break;
      default:
        debugString = "MH.reinvoke";
        break;
    }
    // No pre-action needed.
    return makeReinvokerForm(target, whichCache, constraint, debugString, true, getTargetFn, null);
  }

  /**
   * Create a LF which simply reinvokes a target of the given basic type.
   */
  static LambdaForm makeReinvokerForm(MethodHandle target,
      int whichCache,
      Object constraint,
      String debugString,
      boolean forceInline,
      NamedFunction getTargetFn,
      NamedFunction preActionFn) {
    MethodType mtype = target.type().basicType();
    boolean customized = (whichCache < 0 ||
        mtype.parameterSlotCount() > MethodType.MAX_MH_INVOKER_ARITY);
    boolean hasPreAction = (preActionFn != null);
    LambdaForm form;
    if (!customized) {
      form = mtype.form().cachedLambdaForm(whichCache);
      if (form != null) {
        return form;
      }
    }
    final int THIS_DMH = 0;
    final int ARG_BASE = 1;
    final int ARG_LIMIT = ARG_BASE + mtype.parameterCount();
    int nameCursor = ARG_LIMIT;
    final int PRE_ACTION = hasPreAction ? nameCursor++ : -1;
    final int NEXT_MH = customized ? -1 : nameCursor++;
    final int REINVOKE = nameCursor++;
    LambdaForm.Name[] names = LambdaForm.arguments(nameCursor - ARG_LIMIT, mtype.invokerType());
    assert (names.length == nameCursor);
    names[THIS_DMH] = names[THIS_DMH].withConstraint(constraint);
    Object[] targetArgs;
    if (hasPreAction) {
      names[PRE_ACTION] = new LambdaForm.Name(preActionFn, names[THIS_DMH]);
    }
    if (customized) {
      targetArgs = Arrays.copyOfRange(names, ARG_BASE, ARG_LIMIT, Object[].class);
      names[REINVOKE] = new LambdaForm.Name(target,
          targetArgs);  // the invoker is the target itself
    } else {
      names[NEXT_MH] = new LambdaForm.Name(getTargetFn, names[THIS_DMH]);
      targetArgs = Arrays.copyOfRange(names, THIS_DMH, ARG_LIMIT, Object[].class);
      targetArgs[0] = names[NEXT_MH];  // overwrite this MH with next MH
      names[REINVOKE] = new LambdaForm.Name(mtype, targetArgs);
    }
    form = new LambdaForm(debugString, ARG_LIMIT, names, forceInline);
    if (!customized) {
      form = mtype.form().setCachedLambdaForm(whichCache, form);
    }
    return form;
  }

  static final NamedFunction NF_getTarget;

  static {
    try {
      NF_getTarget = new NamedFunction(DelegatingMethodHandle.class
          .getDeclaredMethod("getTarget"));
    } catch (ReflectiveOperationException ex) {
      throw newInternalError(ex);
    }
  }
}
